package x7.heatctrl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;

/**
 * Web Service for delivering the html and json- filecontent
 * @author Simon
 *
 */
public class UIServlet extends HttpServlet {


	// called for each request on the webserver
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		// fix the path jsut in case someone calls index.php or just / 
		String path = req.getRequestURI().replace(".php", ".html");
		if (path.equals("/")) path="/index.html";
		
		// handle temperature setting
		String troom = req.getParameter("room");
		if (troom!=null && !Config.INSTANCE.setTargetTemp(null, troom, Double.parseDouble(req.getParameter("target")))) {
			// ask all the slaves if they want to handle this target
			for (String slave:System.getProperty("slaves","").split(",")) {
				if (slave.trim().equals("localhost")) continue;
				new URL("http://"+slave+"/setTemp?room="+URLEncoder.encode(troom)+"&target="+req.getParameter("target")).openStream().close();
			}
		}
		
		// return the current state
		if (path.endsWith(".json")) {
			JSONObject result=new JSONObject();
			
			// current metadata
			WeatherChecker weather = Config.INSTANCE.getWeather();
			if (weather!=null)
			   result.put("weather",  Math.round(weather.getCurrentTemperature()*10)/10f );
			result.put("timezone", Config.INSTANCE.getCurrentTimeZone().getDescr());
			result.put("errors", Errors.getErrors());

			// handle servers starting with the own
			JSONArray servers = new JSONArray();
			result.put("servers", servers);
			String localName = System.getProperty("title","master");
			servers.add(localName); 

			// fill the rooms
			JSONArray rooms = new JSONArray();
			result.put("rooms", rooms);
			for (Room r:Config.INSTANCE.getRooms()) {
				JSONObject room=new JSONObject();
				rooms.add(room);
                room.put("id", r.getId());
                room.put("recid", r.getId());
                room.put("current", Math.round(r.getCurrentTemp()*10)/10f);
                room.put("target", Config.INSTANCE.getTargetTemp(r.getId()));
                room.put("state", ((Integer.parseInt(System.getProperty("gpio."+r.getId(),"-1"))<0)?-1:(r.isHeating()?1:0)));
                room.put("descr", System.getProperty("descr."+r.getId(),r.getId()));
                room.put("server", localName);
			}
			
			// get the results from the slaves
			String slaves = System.getProperty("slaves");
			if (slaves!=null && slaves.length()>0) 
				result.put("rooms",fetchSlaves(rooms,servers,slaves.split(",")));

			// deliver the result
			byte[] r =  result.toJSONString().getBytes();
			resp.setContentType("text/json");
			resp.setContentLength(r.length);
			OutputStream out = resp.getOutputStream();
			out.write(r);
			out.close();
			return;
		}
		
		// return the resource found in META-INF
		InputStream in = getClass().getResourceAsStream("/META-INF"+path);
		if (in==null) 
			resp.sendError(404, "Could not find the resoucre "+path);
		else {
			byte[] r =  new byte[2048];
			int read=0;
			if (path.endsWith(".js"))
   			   resp.setContentType("text/javascript");
			else
			   resp.setContentType("text/html");
			OutputStream out = resp.getOutputStream();
			while ((read=in.read(r))>0)
				out.write(r, 0, read);
			out.close();
			in.close();
		}
			
	}

	// run parallel queries for each slave and join them into the one result
	private JSONArray fetchSlaves(JSONArray rooms, JSONArray servers, String[] slaves) {
	  final JSONArray local =rooms;
	  
	  // if the list contains localhost, we need to reorder the result and create an empty resultset
	  if (Arrays.asList(slaves).contains("localhost")) rooms = new JSONArray();
	  
	  // create the tasks
	  Collection<Callable<JSONArray>> tasks = new ArrayList();
	  for (final String slave : slaves) tasks.add(new Callable<JSONArray>() {

		@Override
		public JSONArray call() throws Exception {
			try {
			  if (slave.trim().equals("localhost")) return local;
			  JSONObject result = (JSONObject) new JSONParser(JSONParser.MODE_JSON_SIMPLE).parse(new URL("http://"+slave.trim()+"/result.json").openStream());
			  return result==null?null:(JSONArray)result.get("rooms");
			}
			catch (Exception ex) {
				Errors.log("Error trying to fetch the slave ("+slave+")" , ex);
				return null;
			}
			
		}
	  });
	  
	  // create pool and execute the tasks
	  ExecutorService executor = Executors.newFixedThreadPool(slaves.length);
      try {
		for(Future<JSONArray> result : executor.invokeAll(tasks)){
			  JSONArray slaveResult = result.get();
			  if (slaveResult!=null) {
				  for (JSONObject room:(ArrayList<JSONObject>) (Object)slaveResult) {
					 String server = (String) room.get("server");
					 if (server==null) room.put("server", server="slave");
				     if (!servers.contains(server)) servers.add(server);
				     rooms.add(room);
				  }
			  }
		  }
	} catch (Exception e) {
		Errors.log("Error trying to fetch the slaves ("+slaves+")" , e);
	}
	  executor.shutdown(); 
	  return rooms;
	}
}
